package org.thanos.baseline.service;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.math3.util.Pair;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thanos.baseline.model.BaselineFactors;
import org.thanos.baseline.utils.DateTimeFormatterUtil;
import org.thanos.baseline.utils.TimePointUtils;
import org.thanos.baseline.utils.TimeRangeUtils;
import org.thanos.baseline.vo.QueryVO;
import org.thanos.baseline.vo.ResultVO;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Simple to Introduction
 * className: [BaselineServiceImpl]
 * description: []
 * author: [h241118]
 * createDate: [11/20/2017 10:06 AM]
 */
@Slf4j
@Service("baselineService")
public class BaselineServiceImpl implements BaselineService {

    private static final SecureRandom RANDOM = new SecureRandom();

    private static final String HISTORY_TIME = "historyTime";
    private static final String HISTORY = "history";
    private static final String FACTOR = "factor";

    private static final Map<String, Object> DATA = new HashMap<>();

    @Resource
    private BaselineAlgorithmService algorithmService;

    @Override
    public ResultVO baselineData(QueryVO queryVO) {
        this.generateHistoryData(queryVO);
        this.generateFactorData(queryVO);
        List<String> times = ((List<LocalDateTime>) DATA.get(HISTORY_TIME)).stream().map(localDateTime -> {
            String pattern;
            switch (queryVO.getAggregationType()) {
                case 0:
                    pattern = DateTimeFormatterUtil.YYYY_MM_DD_HH_MM_EN;
                    break;
                case 2:
                    pattern = DateTimeFormatterUtil.YYYY_MM_EN;
                    break;
                default:
                    pattern = DateTimeFormatterUtil.YYYY_MM_DD_EN;
            }
            return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
        }).collect(Collectors.toList());
        List<List<BigDecimal>> factors = (List<List<BigDecimal>>) DATA.get(FACTOR);
        List<BigDecimal> historyDataList = ((List<Pair<LocalDateTime, BigDecimal>>) DATA.get(HISTORY)).stream().map(Pair::getValue).collect(Collectors.toList());
        return new ResultVO(times, factors, historyDataList, this.processBaselineData(queryVO));
    }

    private void generateHistoryData(QueryVO queryVO) {
        Assert.notNull(queryVO.getHistories(), "");
        Assert.notNull(queryVO.getAggregationType(), "");
        LocalDateTime endDay = TimePointUtils.lastSecondOfTheDay(LocalDateTime.now());
        LocalDateTime timePoint;
        switch (queryVO.getAggregationType()) {
            case 0:
                timePoint = endDay.minusHours(queryVO.getHistories());
                break;
            case 2:
                timePoint = endDay.minusMonths(queryVO.getHistories());
                break;
            default:
                timePoint = endDay.minusDays(queryVO.getHistories());
        }
        // 获取当前时间之前60天，作为组织数据的开始时间
        LocalDateTime startDay = TimePointUtils.firstSecondOfTheDay(timePoint);
        List<LocalDateTime> times = TimeRangeUtils.getTimeRangeList(startDay, endDay, queryVO.getAggregationType());
        DATA.put(HISTORY_TIME, times);
        DATA.put(HISTORY, times.stream().map(localDateTime -> Pair.create(localDateTime, BigDecimal.valueOf(RANDOM.nextDouble() * 1000).setScale(2, RoundingMode.HALF_UP))).collect(Collectors.toList()));
    }

    private void generateFactorData(QueryVO queryVO) {
        Assert.notNull(queryVO.getFactors(), "");
        Assert.isTrue(queryVO.getFactors() < 9, "");
        Assert.notNull(DATA.get(HISTORY_TIME), "");
        List<LocalDateTime> times = (List<LocalDateTime>) DATA.get(HISTORY_TIME);
        List<List<BigDecimal>> factors = times.stream().map(localDateTime -> {
            List<BigDecimal> result = new ArrayList<>();
            for (int i = 0; i < queryVO.getFactors(); i++) {
                result.add(BigDecimal.valueOf(RANDOM.nextDouble()).setScale(4, RoundingMode.HALF_UP));
            }
            return result;
        }).collect(Collectors.toList());
        DATA.put(FACTOR, factors);
    }

    /**
     * 计算baseline
     *
     * @param queryVO
     * @return
     */
    private BigDecimal processBaselineData(QueryVO queryVO) {
        Assert.notNull(DATA.get(HISTORY), "");
        Assert.notNull(DATA.get(FACTOR), "");
        List<Pair<LocalDateTime, BigDecimal>> historyDataList = (List<Pair<LocalDateTime, BigDecimal>>) DATA.get(HISTORY);
        List<List<BigDecimal>> factorDataList = (List<List<BigDecimal>>) DATA.get(FACTOR);
        List<BaselineFactors> baselineFactorsList = new ArrayList<>(historyDataList.size());
        for (int i = 0; i < historyDataList.size(); i++) {
            baselineFactorsList.add(this.buildBaselineFactors(historyDataList.get(i).getValue(), factorDataList.get(i)));
        }
        return algorithmService.predict(queryVO.getFactors(), baselineFactorsList);
    }

    /**
     * 构建训练集
     *
     * @param value
     * @param factors
     * @return
     */
    private BaselineFactors buildBaselineFactors(BigDecimal value, List<BigDecimal> factors) {
        BaselineFactors baselineFactors = new BaselineFactors();
        baselineFactors.setYFactor(value);
        try {
            for (int i = 0; i < factors.size(); i++) {
                String methodName = "setFactor" + (i + 1);
                Method method = baselineFactors.getClass().getDeclaredMethod(methodName, BigDecimal.class);
                method.invoke(baselineFactors, factors.get(i));
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return baselineFactors;
    }
}
